/**
 * Copyright 2006 gworks.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 */
package org.javaongems.client.forms;

import java.util.HashMap;
import java.util.Map;

import org.javaongems.client.Controller;
import org.javaongems.client.Coordinator;
import org.javaongems.client.Gem;
import org.javaongems.client.View;
import org.javaongems.client.rt.io.AsyncCallbackFilter;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventPreview;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.KeyboardListenerCollection;
import com.google.gwt.user.client.ui.MouseListener;
import com.google.gwt.user.client.ui.MouseListenerAdapter;
import com.google.gwt.user.client.ui.Widget;

/**
 * <p>Provides standard form handling for a form component.</p>
 * <p>Responsibilities:
 * <ul>
 * 	<li> construct and register border action, move and resize listeners
 * 	<li> construct async callback decorations for subsequent service calls
 * 	<li> notify coordinator of controller opens and closures
 * 	<li> handle/dispatch form events
 *  <li> reduce the amount of scaffolding code required in application controllers
 * </ul>	
 * </p>
 * <p>Collaborators:
 * <ul>
 * 	<li> Form - provider of the widgets and events that are subscribed to
 *  <li> {@link Gem} (coordinator)
 * 	<li> {@link FormActionBarHandler} - provides border icon handling
 *  <li> {@link FormMoverHandler} - provides title bar drap handling
 *  <li> {@link FormResizeHandler} - provides resize grab handle handling
 * </ul>
 * </p>
 * 
 * @author Ashin Wimalajeewa (ash)
 */
abstract public class FormController extends FormActionAdapter 
		implements 	Controller,
					EventPreview,
					AsyncCallback
					{
	final static public String CTRLR_FORM = "controller.form";

	protected HashMap 		cargo = new HashMap();

	protected boolean 		dragging, mouseDown;
	protected int 			dragStartX, dragStartY, dragOffsetX, dragOffsetY;
	protected boolean 		ignoreResetResize;
	protected MouseListener	formActionsListener;
	protected MouseListener	formMoveListener;
	protected MouseListener formResizeListener;
	protected AsyncCallbackFilter 		callback;
	protected FormLoadStatusCallback	formLoadCallbackDecoration;

	public FormController() {
		formActionsListener = createFormActionsListener();
		formMoveListener = createFormMoveListener();
		formResizeListener = createFormResizeListener();
		callback = createAsyncCallBack();
		formLoadCallbackDecoration = (FormLoadStatusCallback) callback.getDecoration(FormLoadStatusCallback.HNDL);
	}
	
	/**
	 * Command that returns a mouse listener implementation for the border icons 
	 * action bar (ie. min, max & close).  
	 * @return a handler capable of dealing with mouse events for the border actions
	 * @see FormActionBarHandler
	 */
	protected MouseListener createFormActionsListener() {
		return new FormActionBarHandler();
	}

	/**
	 * Command that returns a mouse listener implementation for the title bar.  
	 * @return a handler capable of dealing with mouse events for title bar dragging
	 * @see FormMoverHandler
	 */
	protected MouseListener createFormMoveListener() {
		return new FormMoverHandler();
	}

	/**
	 * Command that returns a mouse listener implementation for the resize handle.  
	 * @return a handler capable of dealing with mouse events for resize handle dragging
	 * @see FormResizeHandler
	 */
	protected MouseListener createFormResizeListener() {
		return new FormResizeHandler();
	}

	/**
	 * Command that creates an async callback filter with wrapped decorations
	 * @return a callback filter with expected support for FormLoadStatusCallback
	 * @see Coordinator#decorateCallback()
	 * @see Gem#stone
	 */
	protected AsyncCallbackFilter createAsyncCallBack() {
		AsyncCallbackFilter ret = Gem.stone.decorateCallback(this, new Object[] {getForm()});
		return ret;
	}
	
	/**
	 * Command that notifies the coordinator of the opened controller
	 * @see org.javaongems.client.Controller#setUp(java.util.Map)
	 * @see Coordinator#openController
	 * @see Gem#stone
	 */
	public void setUp(Map cfg) {
		Gem.stone.openController(this);
	}

	/**
	 * Command that gets the form object from the cargo and invokes show() on it.
	 * @see org.javaongems.client.Controller#run()
	 */
	public void run() {
		getForm().show();
	}

	/**
	 * Command that closes the form, clears the cargo and notifies the 
	 * coordinator of the closure
	 * @see org.javaongems.client.Controller#tearDown()
	 * @see Coordinator#closeController()
	 * @see Gem#stone
	 */
	public void tearDown() {
		getForm().close();
		cargo.clear();
		Gem.stone.closeController(this);
	}
	
	/**
	 * Event handler invoked on form closing events. This method delegates
	 * the responsibility to the onFromCloseRequest() method.
	 * @see org.javaongems.client.Controller#containerCloseRequest()
	 * @see #onFormCloseRequest()
	 */
	public String containerCloseRequest() {
		return onFormCloseRequest();
	}
	
	/**
	 * Event handler invoked when the form's visibility is toggled. If the form
	 * object is modal, then is will add event preview features to eat
	 * events that are not targetted at the current form.
	 * 
	 * @see org.javaongems.client.forms.FormActionAdapter#onFormShow()
	 * @see DOM#addEventPreview(EventPreview)
	 */
	public void onFormShow() {
		if (getForm().isModal()) 
			DOM.addEventPreview(this);
	}

	/**
	 * Event handler invoked when the form's visibility is toggled. If the form
	 * object is modal, then this will remove event preview features that was
	 * eating events targetted at other objects that are not part of the form. 
	 * 
	 * @see org.javaongems.client.forms.FormActionAdapter#onFormHide()
	 * @see DOM#removeEventPreview(EventPreview)
	 */
	public void onFormHide() {
		if (getForm().isModal()) 
			DOM.removeEventPreview(this);
	}

	/**
	 * Event handler invoked when the form has been closed. If the form
	 * object is modal, then this will remove event preview features that was
	 * eating events targetted at other objects that are not part of the form. It
	 * also calls tearDown() on the controller. 
	 * 
	 * @see org.javaongems.client.forms.FormActionAdapter#onFormClosed()
	 * @see DOM#removeEventPreview(EventPreview)
	 * @see #tearDown()
	 */
	public void onFormClosed() {
		if (getForm().isModal()) 
			DOM.removeEventPreview(this);
		tearDown();
	}

	/** 
	 * Query the cargo associated with this controller.
	 * @see org.javaongems.client.Controller#getCargo()
	 */
	public Map getCargo() {
		return cargo;
	}

	/**
	 * Command that subscribes all handlers to the form. It also assigns
	 * the form to the async callback for status reporting.  
	 * @param wnd - the form "view object" that this controller is supporting
	 */
	public void setForm(Form wnd) {
		cargo.put(CTRLR_FORM, wnd);
		wnd.titlePanel.addMouseListener(formMoveListener);
		wnd.closeButton.addMouseListener(formActionsListener);
		wnd.minButton.addMouseListener(formActionsListener);
		wnd.maxButton.addMouseListener(formActionsListener);
		wnd.resizeHandle.addMouseListener(formResizeListener);
		formLoadCallbackDecoration.setForm(wnd);
	}

	/**
	 * Queries the Form object
	 * @see org.javaongems.client.Controller#getView()
	 */
	public View getView() {
		return getForm();
	}

	/**
	 * @return the form object that is stored in the cargo
	 */
	public Form getForm() {
		return (Form) cargo.get(CTRLR_FORM);
	}
	
	// event preview implementation
	/**
	 * Event handler that is invoked only when a form is modal and events must be
	 * intercepted and eaten if not targetting the controllers form.
	 * @see com.google.gwt.user.client.EventPreview#onEventPreview(com.google.gwt.user.client.Event)
	 * @see #onFormShow()
	 * @see #onFormHide()
	 * @see #onFormClosed()
	 */
	public boolean onEventPreview(Event event) {
		int type = DOM.eventGetType(event);
		switch (type) {
			case Event.ONKEYDOWN: {
				return onKeyDownPreview((char)DOM.eventGetKeyCode(event),
						KeyboardListenerCollection.getKeyboardModifiers(event));
			}
			case Event.ONKEYUP: {
				return onKeyUpPreview((char)DOM.eventGetKeyCode(event),
						KeyboardListenerCollection.getKeyboardModifiers(event));
			}
			case Event.ONKEYPRESS: {
				return onKeyPressPreview((char)DOM.eventGetKeyCode(event),
						KeyboardListenerCollection.getKeyboardModifiers(event));
			}
			case Event.ONMOUSEDOWN:
			case Event.ONMOUSEUP:
			case Event.ONMOUSEMOVE:
			case Event.ONCLICK:
			case Event.ONDBLCLICK: {
				if (DOM.getCaptureElement() == null) {
					Element target = DOM.eventGetTarget(event);
					if (!DOM.isOrHasChild(getForm().getElement(), target)) 
						return false;
				}
			}
		}
		return true;
	}
	
	public boolean onKeyUpPreview(char key, int modifiers) {
		return true;
	}

	public boolean onKeyDownPreview(char key, int modifiers) {
		return true;
	}
	
	public boolean onKeyPressPreview(char key, int modifiers) {
	    return true;
	}
	
	public AsyncCallbackFilter getOuterCallback() {
		return callback;
	}

	public FormLoadStatusCallback getFormLoadCallback() {
		return formLoadCallbackDecoration;
	}
	
	// empty async callback implementation
	/**
	 * Event handler for successful service invocations.
	 * @see com.google.gwt.user.client.rpc.AsyncCallback#onSuccess(java.lang.Object)
	 * @see #createAsyncCallBack()
	 */
	public void onSuccess(Object result) {
		// nothing to do
	}

	/**
	 * Event handler for failed service invocations.
	 * @see com.google.gwt.user.client.rpc.AsyncCallback#onFailure(java.lang.Throwable)
	 * @see #createAsyncCallBack()
	 */
	public void onFailure(Throwable caught) {
		// nothing to do
	}
	
	/**
	 * <p>Provides default handling for mouse down on the close, min and max 
	 * buttons</p>
	 */
	protected class FormActionBarHandler extends MouseListenerAdapter {
		public void onMouseDown(Widget sender, int x, int y) {
			Form wnd = getForm();
			if (!sender.isVisible())
				return;
			boolean cont = false;
			if (sender == wnd.closeButton) {
				String msg = containerCloseRequest();
				cont = (msg == null);
				if (!cont)
					Window.alert(msg);
			} else if (sender == wnd.minButton)
				cont = onFormMinimise();
			else if (sender == wnd.maxButton)
				cont = onFormMaximise();
			if (!cont)
				return;
			if (sender == wnd.closeButton)
				wnd.close();
			else if (sender == wnd.minButton)
				wnd.hide();
			else if (sender == wnd.maxButton) {
				wnd.container.setWidth("100%");
				wnd.container.setHeight("100%");
				int width = wnd.container.getOffsetWidth() - 23, height = wnd.container
						.getOffsetHeight() - 23;
				wnd.container.setPixelSize(width, height);
				onFormResized();
			}
		}
	}

	/**
	 * Provides default drag handling for the resize grabhandle widget
	 */
	protected class FormResizeHandler extends MouseListenerAdapter {
		public void onMouseDown(Widget sender, int x, int y) {
			Form wnd = getForm();
			dragging = true;
			DOM.setCapture(wnd.resizeHandle.getElement());
			dragStartX = x;
			dragStartY = y;
		}

		public void onMouseMove(Widget sender, int x, int y) {
			if (dragging) {
				Form wnd = getForm();
				int width = x + wnd.getOffsetWidth() - dragStartX;
				int height = y + wnd.getOffsetHeight() - dragStartY;
				wnd.setPixelSize(width, height);
			}
		}

		public void onMouseUp(Widget sender, int x, int y) {
			dragging = false;
			DOM.releaseCapture(getForm().resizeHandle.getElement());
			onFormResized();
		}
	}

	/**
	 * Provides default drag handling for the titlePanel widget
	 */
	protected class FormMoverHandler extends MouseListenerAdapter {
		public void onMouseDown(Widget sender, int x, int y) {
			Form wnd = getForm();
			dragging = true;
			DOM.setCapture(wnd.titlePanel.getElement());
			dragStartX = x;
			dragStartY = y;
		}

		public void onMouseMove(Widget sender, int x, int y) {
			if (dragging) {
				Form wnd = getForm();
				int left = x + wnd.getAbsoluteLeft() - dragStartX;
				int top = y + wnd.getAbsoluteTop() - dragStartY;
				wnd.setPosition(left, top);
			}
		}

		public void onMouseUp(Widget sender, int x, int y) {
			dragging = false;
			DOM.releaseCapture(getForm().titlePanel.getElement());
			onFormMoved();
		}
	}
}